﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Collections;
using eeenno.ReaderEngine.Utilities;

namespace eeenno.ReaderEngine.Adapters
{
    public class GoogleReaderEngine
    {
        private static int MAX_NUMBER_OF_ARTICLES_SINCE_TIME = 1000;
        private static int NUMBER_OF_CONN_RETRIES = 2;
        private static string _SID_URI = "https://www.google.com/accounts/ClientLogin";
        private static string _API_URI = "http://www.google.com/reader/api/0/";
        private static string _ATOM_URI = "http://www.google.com/reader/atom/";
        private static string _MARK_ALL_READ_URI = "mark-all-as-read";
        private static string _ARTICLES_BY_STATE_URI = "user/-/state/com.google/";
        private static string _ARTICLES_BY_TAG_URI = "user/-/label/";
        private static string _ARTICLES_BY_FEED_URI = "feed/";
        private static string _GET_TAG_LIST_URI = "tag/list?output=xml";
        private static string _GET_SUBSCRIPTION_LIST_URI = "subscription/list?output=xml";
        private static string _GET_UNREAD_COUNT_URI = "unread-count";
        private static string _EDIT_TAG_URI = "edit-tag";

        private static string _SUBSCRIPTION_EDIT_URI = "subscription/edit";

        private static string _ITEM_PARAMETER = "i=";
        private static string _ADD_STATE_PARAMETER = "a=";
        private static string _REM_STATE_PARAMETER = "r=";
        private static string _ACTION_STATE_PARAMETER = "ac=";
        
        //parameters for accessing reader items that are returned as atoms
        private static string _START_TIME_PARAMETER = "ot=";
        private static string _ORDER_PARAMETER = "r=";
        private static string _QUANTITY_PARAMETER = "n=";

        private static string _READ_LABEL = "read";
        private static string _STARRED_LABEL = "starred";
        private static string _BROADCAST_LABEL = "broadcast";
        private static string _UNREAD_LABEL = "kept-unread";
        private static string _TRACKING_UNREAD_LABEL = "tracking-kept-unread";

        private static string _TITLE_PARAMETER = "t=";
        private static string _ID_PARAMETER = "s=";

        private static string _SUB_FEED_PARAMETER = "s=";

        private static string _SUB_ADD_PARAMETER = "a=";
        private static string _SUB_REM_PARAMETER = "r=";
        private static string _SUB_ACTION_PARAMETER = "ac=";
        private static string _TOKEN_PARAMETER = "T=";

        private static string _CONT_PARAMETER = "c=";

        private static string _AUTH_PARAMETER = "Auth=";

        private static string _username = String.Empty;
        private static string _password = String.Empty;
        private static string _token = String.Empty;
        private static string _sid = String.Empty;
        private static string _auth = String.Empty;

        public static string auth
        {
            get { return GoogleReaderEngine._auth; }
        }

        private static bool _is_connected;

        public static string username
        {
            set { _username = value; }
        }

        public static string password
        {
            set { _password = value; }
        }

        public static bool is_connected
        {
            get { return _is_connected; }
        }

        public static string sid
        {
            get { return _sid; }
        }

        public static string token
        {
            get { return _token; }
        }

        /// <summary>
        /// Requests SID and token information from Google services
        /// </summary>
        /// <param name="username">Google username (before @)</param>
        /// <param name="password">Google password</param>
        public static void Connect(string username, string password)
        {
            _username = username;
            _password = password;

            _sid = GetSID(_username, _password);
            if (!sid.Equals(WebManager.SR_ERROR) && _sid.Length > 0)
            {
                _token = GetToken(_sid);
                if (!_token.Equals(WebManager.SR_ERROR) && _token.Length > 0)
                {                   
                    _is_connected = true;
                }
            }            
            else
            {               
                _is_connected = false;
            }            
        }

        private static string GetToken(string sid)
        {
#if DEBUG
            LogManager.LogDebug("GoogleReaderEngine.GetToken", "Entering...");
#endif
            for (int i = 0; i < NUMBER_OF_CONN_RETRIES; i++)
            {
                HttpWebRequest token_request = WebManager.GetWebRequest(_API_URI + "token", false);
#if DEBUG
                LogManager.LogDebug("GoogleReaderEngine.GetToken", "Url = " + token_request.RequestUri.OriginalString);
#endif
                if (token_request != null)
                {
#if DEBUG
                    LogManager.LogDebug("GoogleReaderEngine.GetToken", "Token request is not null");
#endif
                    token_request.Headers.Add("Cookie", "SID=" + sid);
                    return WebManager.GetResponseStreamData(token_request);
                }
            }
            return string.Empty;
        }

        private static string GetSID(string username, string password)
        {
#if DEBUG
            LogManager.LogDebug("GoogleReaderEngine.GetSID", "Entering...");
#endif
            password = password.Replace("&", "%26");
            HttpWebRequest sid_request = WebManager.GetWebRequest(_SID_URI, true);
            if (sid_request != null)
            {
#if DEBUG
                LogManager.LogDebug("GoogleReaderEngine.GetSID", "Adding post data to http request");
#endif
                UTF8Encoding encoding = new UTF8Encoding();
                string post_data = "Email=" + username + "&Passwd=" + password + 
                    "&source=SpeeedReader&service=reader";
                byte[] data = encoding.GetBytes(post_data);
#if DEBUG
                LogManager.LogDebug("GoogleReaderEngine.GetSID", "Post data size = " + post_data.Length);
#endif
                WebManager.bytes_transferred += sid_request.ContentLength = data.Length;
                try
                {
                    using (Stream request_stream = sid_request.GetRequestStream())
                    {
#if DEBUG
                        LogManager.LogDebug("GoogleReaderEngine.GetSID", "Writing data to the request stream");
#endif
                        request_stream.Write(data, 0, data.Length);
                    }
                }
                catch (Exception e)
                {
                    LogManager.LogError("GoogleReaderEngine.GetSID", e.Message);
                    return String.Empty;
                }

                string sid = WebManager.GetResponseStreamData(sid_request);
#if DEBUG
                LogManager.LogDebug("GoogleReaderEngine.GetSID", "The SID is " + sid);
#endif
                if (sid == WebManager.SR_OFFLINE)
                {
                    return String.Empty;
                }
                if (sid.Equals(WebManager.SR_ERROR))
                {
                    return sid;
                }
                _auth = ParseAuth(sid);
                return ParseSID(sid);
            }
            return String.Empty;
        }

        private static string MakeAPICall(string function, bool isPost, string post_params)
        {
            string ret_val = String.Empty;
            UTF8Encoding encoding = new UTF8Encoding();
            HttpWebRequest request = null;

            for (int i = 0; i < NUMBER_OF_CONN_RETRIES; i++)
            {
                if (_is_connected)
                {     
                    post_params += "&" + _TOKEN_PARAMETER + _token;
                    string call_uri = _API_URI + function;
                    request = WebManager.GetWebRequest(call_uri, isPost, _sid, _token, _auth);
                    if (request != null)
                    {
                        byte[] data = encoding.GetBytes(post_params);
                        WebManager.bytes_transferred += request.ContentLength = data.Length;
                        try
                        {
                            using (Stream request_stream = request.GetRequestStream())
                            {
                                request_stream.Write(data, 0, data.Length);
                            }
                        }
                        catch (Exception e)
                        {
                            LogManager.LogError("GoogleReaderEngine.MakeAPICall", e.Message);
                            return String.Empty;
                        }
                        ret_val = WebManager.GetResponseStreamData(request);
                        if (ret_val == WebManager.SR_OFFLINE)
                        {
                            return String.Empty;
                        }
                        //if SR error is returned, token is most likely expired
                        //reconnect to new token
                        RefreshToken(ret_val);
                        break;
                    }
                }
                else
                {
                    Connect(_username, _password);
                }
            }

            return ret_val;
        }

        private static string MakeAPICall(string function)
        {
            string ret_val = String.Empty;
            HttpWebRequest request = null; 

            for (int i = 0; i < NUMBER_OF_CONN_RETRIES; i++)
            {
                if (_is_connected)
                {                   
                    string call_uri = _API_URI + function;
                    request = WebManager.GetWebRequest(call_uri, false, _sid, _token, _auth);
                    if (request != null)
                    {
                        ret_val = WebManager.GetResponseStreamData(request);
                        if (ret_val == WebManager.SR_OFFLINE)
                        {
                            return String.Empty;
                        }
                        //if SR error is returned, token is most likely expired
                        //reconnect to new token
                        RefreshToken(ret_val);
                        break;
                    }
                }
                else
                {
                    Connect(_username, _password);
                }
            }

            return ret_val;
        }

        private static string MakeAtomCall(string function)
        {
            string ret_val = String.Empty;
            HttpWebRequest request = null;

            for (int i = 0; i < NUMBER_OF_CONN_RETRIES; i++)
            {
                if (_is_connected)
                {                    
                    string uri = _ATOM_URI + function;
                    request = WebManager.GetWebRequest(uri, false, _sid, _token, _auth);
                    if (request != null)
                    {
                        ret_val = WebManager.GetResponseStreamData(request);
                        if (ret_val == WebManager.SR_OFFLINE)
                        {
                            return String.Empty;
                        }
                        //if SR error is returned, token is most likely expired
                        //reconnect to new token
                        RefreshToken(ret_val);
                        break;
                    }
                }
                else
                {
                    Connect(_username, _password);
                }
            }

            return ret_val;
        }

        /// <summary>
        /// Processes value, if contains error, refresh token
        /// </summary>
        /// <param name="ret_val"></param>
        private static void RefreshToken(string value)
        {
            //if SR error is returned, token is most likely expired
            //reconnect to new token
            if (value.Equals(WebManager.SR_ERROR))
            {
                Connect(_username, _password);
                //_token = GetToken(_sid);
            }
        }

        /// <summary>
        /// Retrieves the Auth from the string received from Google
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        private static string ParseAuth(string response)
        {
            string ret_val = String.Empty;
            try
            {
                int start = response.IndexOf(_AUTH_PARAMETER) + 5;
                //int finish = response.Length - 2;
                return response.Substring(start).Replace("\n", "");
            }
            catch (ArgumentOutOfRangeException e)
            {
                return String.Empty;
            }
            return ret_val;
        }

        /// <summary>
        /// Retrieves the SID from the string received from Google
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        public static string ParseSID(string response)
        {
            try
            {
                int start = response.IndexOf("SID=") + 4;
                int finish = response.IndexOf("LSID=") - 5;
                return response.Substring(start, finish);
            }
            catch (ArgumentOutOfRangeException e)
            {
                return String.Empty;
            }
        }

        #region Contains all specific Google Reader API actions

        /// <summary>
        /// Adds a state to an article item
        /// </summary>
        /// <param name="item_id"></param>
        /// <param name="state_to_add"></param>
        private static void AddState(string item_id, string state_to_add)
        {
            MakeAPICall(_EDIT_TAG_URI + "?client=contact:" + _username, true, _ITEM_PARAMETER + item_id +
                "&" + _ADD_STATE_PARAMETER + _ARTICLES_BY_STATE_URI + state_to_add);
        }

        /// <summary>
        /// Adds a state to an article item
        /// </summary>
        /// <param name="item_id"></param>
        /// <param name="state_to_add"></param>
        private static void RemoveState(string item_id, string state_to_remove)
        {
            MakeAPICall(_EDIT_TAG_URI + "?client=contact:" + _username, true, _ITEM_PARAMETER + item_id +
                "&" + _REM_STATE_PARAMETER + _ARTICLES_BY_STATE_URI + state_to_remove);
        }

        /// <summary>
        /// Adds a state to an article item
        /// </summary>
        /// <param name="item_id"></param>
        /// <param name="state_to_add"></param>
        private static void AddRemoveState(string item_id, string state_to_add, string state_to_remove)
        {
            MakeAPICall(_EDIT_TAG_URI + "?client=contact:" + _username, true, _ITEM_PARAMETER + item_id +
                "&" + _REM_STATE_PARAMETER + _ARTICLES_BY_STATE_URI + state_to_remove +
                "&" + _ADD_STATE_PARAMETER + _ARTICLES_BY_STATE_URI + state_to_add +
                "&" + _ACTION_STATE_PARAMETER + "edit");
        }

        public static string GetFeedData(string feed, int quantity)
        {
            feed = feed.Replace("?", "%3F");
            return MakeAtomCall(feed + "?" + _QUANTITY_PARAMETER + quantity);
        }

        /// <summary>
        /// Gets the RSS feed data beginning with the given time
        /// </summary>
        /// <param name="feed_uri"></param>
        /// <param name="date_time">unix time from 1/1/1970</param>
        /// <returns></returns>
        public static string GetFeedData(string feed, string date_time)
        {
            feed = feed.Replace("?", "%3F");
            return MakeAtomCall(feed + "?" + _ORDER_PARAMETER + "o" + 
                "&" + _START_TIME_PARAMETER + date_time + "&" + _QUANTITY_PARAMETER + MAX_NUMBER_OF_ARTICLES_SINCE_TIME);
        }

        /// <summary>
        /// Retrieves the xml representing all the articles with the requested state & quantity
        /// </summary>
        /// <param name="state">article state</param>
        /// <param name="quantity">number of articles desired</param>
        /// <returns>xml representing all the articles with the requested state</returns>
        public static string GetArticlesByState(string state, int quantity)
        {
            return MakeAtomCall(_ARTICLES_BY_STATE_URI + state + "?" + 
                _QUANTITY_PARAMETER + quantity.ToString());
        }

        /// <summary>
        /// Retrieves the xml representing all the articles with the requested tag & quantity
        /// </summary>
        /// <param name="tag">article tag</param>
        /// <param name="quantity">number of articles desired</param>
        /// <returns>xml representing all the articles with the requested tag</returns>
        public static string GetArticlesByTag(string tag, int quantity)
        {
            return MakeAtomCall(_ARTICLES_BY_TAG_URI + tag + "?" + 
                _QUANTITY_PARAMETER + quantity.ToString());
        }

        public static string GetArticlesByTag(string tag, string timestamp)
        {
            return MakeAtomCall(_ARTICLES_BY_TAG_URI + tag + "?" +
                _ORDER_PARAMETER + "o" + "&" + _START_TIME_PARAMETER + timestamp + "&" + _QUANTITY_PARAMETER
                 + MAX_NUMBER_OF_ARTICLES_SINCE_TIME);
        }

        /// <summary>
        /// Retrieves the xml representing all the articles in this feed
        /// </summary>
        /// <param name="feed"></param>
        /// <param name="quantity"></param>
        /// <returns></returns>
        public static string GetArticlesByFeed(string feed, int quantity)
        {
            feed = feed.Replace("?", "%3F");
            return MakeAtomCall(feed + "?" + _QUANTITY_PARAMETER + quantity.ToString());
        }

        /// <summary>
        /// Retrieves the next group of articles of a feed
        /// </summary>
        /// <param name="continuation"></param>
        /// <param name="feed"></param>
        /// <returns></returns>
        public static string GetMoreFeedArticles(string continuation, string feed, int quantity)
        {
            feed = feed.Replace("?", "%3F");
            return MakeAtomCall(feed  + "?" + _CONT_PARAMETER + continuation + "&" + 
                _QUANTITY_PARAMETER + quantity.ToString());
        }

        public static string GetMoreTagArticles(string continuation, string tag, int quantity)
        {
            tag = tag.Replace("?", "%3F");
            return MakeAtomCall(_ARTICLES_BY_TAG_URI + tag + "?" +
                _QUANTITY_PARAMETER + quantity.ToString() + "&" + _CONT_PARAMETER + continuation);
        }

        /// <summary>
        /// Retrieves the xml representing the list of tags
        /// </summary>
        /// <returns>xml representing the list of tags</returns>
        public static string GetTagList()
        {
            return MakeAPICall(_GET_TAG_LIST_URI);
        }

        /// <summary>
        /// Retrieves the xml representing complete subscription list
        /// </summary>
        /// <returns>xml representing the complete subscription list</returns>
        public static string GetSubscriptionList()
        {
            return MakeAPICall(_GET_SUBSCRIPTION_LIST_URI);
        }

        /// <summary>
        /// Retrieves the xml representing the article unread count
        /// </summary>
        /// <returns>xml representing the article unread count</returns>
        public static string GetUnreadCount()
        {
            return MakeAPICall(_GET_UNREAD_COUNT_URI);
        }

        public static void MarkArticleStarred(string article_id)
        {
            AddState(article_id, _STARRED_LABEL);
        }

        public static void MarkArticleBroadcast(string article_id)
        {
            AddState(article_id, _BROADCAST_LABEL);
        }

        public static void MarkArticleUnread(string article_id)
        {
            AddRemoveState(article_id, _UNREAD_LABEL, _READ_LABEL);
            AddState(article_id, _TRACKING_UNREAD_LABEL);
        }

        public static void MarkArticleRead(string article_id)
        {
            AddState(article_id, _READ_LABEL);
        }

        public static void MarkAllAsRead(string id, string title)
        {
            id = id.Replace("&", "%26");
            id = id.Replace("=", "%3D");
            id = id.Replace("?", "%3F");
            MakeAPICall(_MARK_ALL_READ_URI + "?client=scroll&hl=en", true, _ID_PARAMETER + id + "&" + _TITLE_PARAMETER + title );
        }

        #endregion
    }
}